package mirabilis.org.util;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.jblas.DoubleMatrix;
import org.jblas.Solve;

import mirabilis.org.config.Configuration;
import mirabilis.org.simplices.Simplex;
import mirabilis.org.termination.Termination;

public class Bounds {

	/**
	 * 
	 * @param A any point in the line
	 * @param B any point in the line which is different to @param A
	 * @return The two intersecting points of the line defined by the direction vector AB with the boundary hyperplanes
	 */
	public static double[][] findBoundsOfLine(double[] A, double[] B) throws Exception{
		
		if(A.equals(B)) throw new Exception("A and B must be different points");
		
		double[] vector = new double[A.length];
		for(int i=0;i<vector.length;i++)
			vector[i] = B[i] - A[i];
		
		//1. Creating a list with all intersecting hyperplanes (the box!!)
		List<double[]> interectingHyperplanes = new ArrayList<double[]> ();
		
		for(int i=0;i<Configuration.N;i++){
			interectingHyperplanes.add(hyperplaneAtTheBound(i*2));
			interectingHyperplanes.add(hyperplaneAtTheBound((i*2)+1));
		}
		
		//2. Creating a list with intersecting points at the boundaries.
		// If the line is outside the boundaries it should return an empty list
		// otherwise:
		// A line should usually intersect the box in two points
		// but there might be also the rare case in which a line intersects a box in one corner 
		List<double[]> intersectingPoints = new ArrayList<double[]> ();
		for (Iterator<double[]> it= interectingHyperplanes.iterator();it.hasNext();){
			double[] hyperplane = it.next();
			double[] intersectingpoint = Gradients.getIntersectionOfaVectorWithHyperplane(vector.clone(), A.clone(), hyperplane);
			

		
			if (intersectingpoint == null)
				throw new Exception();
			else if(!isCoordinateOutsideTheBounds(intersectingpoint)){
				intersectingPoints.add(intersectingpoint);	
			}
		}
		//System.err.println("Intersecting poitns:"+intersectingPoints.size());
//		System.err.println("Bounds.findBoundsOfFile No. of intersecting points: "+intersectingPoints.size()+" inter. HypP: "+interectingHyperplanes.size());
//		if(intersectingPoints.size()!=2){	
//			for (Iterator<double[]> it= intersectingPoints.iterator();it.hasNext();){
//				double[] point = it.next();
//				for(int i=0;i<point.length;i++)
//					System.err.print(point[i]+",");
//				System.err.println();
//			}
//		}
		
		//System.err.println("intersect "+intersectingPoints.size());
		

		//3. We return the intersecting points
		double[][] boundsofline = new double[2][A.length];
		if (intersectingPoints.size() == 2){
			boundsofline[0] = intersectingPoints.get(0);
			boundsofline[1] = intersectingPoints.get(1);
			return boundsofline;
		}else if(intersectingPoints.size() == 1){
			boundsofline[0] = intersectingPoints.get(0);
			boundsofline[1] = null;
		}else
			boundsofline = null;
		
		return boundsofline;
	}
	
	/**
	 * 
	 * @param coordinate A given point in the hyperspace
	 * @return true if the given point is outside the bounds (the box in which the function is defined), otherwise return false
	 */
	public static boolean isCoordinateOutsideTheBounds(double[] coordinate){
		boolean outsideTheBounds = false;
		
		for(int i=0;i<Configuration.N;i++){
			if(coordinate[i] < Configuration.bounds[i*2]){//lower bound
				outsideTheBounds = true;
			}else if (coordinate[i] > Configuration.bounds[(i*2)+1]){//upper bound
				outsideTheBounds = true;
			}
		}
		
		return outsideTheBounds;
	}
	
	public static double[] closestIntersectingBoundToAPoint(double[] coordinate,double[][] boundsofline){
		if(norm(coordinate, boundsofline[0])<norm(coordinate, boundsofline[1])){
			return boundsofline[0];
		}else
			return boundsofline[1];
	}
	
	
	public static double fitnessWithInterpolationAtTheBounds(double[] coordinates, Simplex simplex){
		
		boolean[] lu_planes = new boolean[Configuration.N*2];
		boolean outsideTheBounds = false;
		
		//----------------------------------------------------------
		//Record all possible intersecting hyperplanes at the bounds 
		//----------------------------------------------------------
		for(int i=0;i<Configuration.N;i++){
			if(coordinates[i] < Configuration.bounds[i*2]){
				lu_planes[i*2] = true;
				outsideTheBounds = true;
			}else if (coordinates[i] > Configuration.bounds[(i*2)+1]){
				lu_planes[(i*2)+1] = true;
				outsideTheBounds = true;
			}
		}
		
		//----------------------------------------------------------
		// If coordinates are within the bounds the function is defined and we return the true fitness
		//----------------------------------------------------------
		if(!outsideTheBounds){
			Termination.increment_evaluation();
			return simplex.getEvaluator().evaluate(coordinates);
		}else{
			//System.out.println("Out of bounds "+doubleArrayAsString(coordinates));
		}
		
		//----------------------------------------------------------
		//Otherwise we return an interpolated fitness value (Mind that this one does not require a real evaluation)
		//----------------------------------------------------------
		
		//1. Creating a list with all possible intersecting hyperplanes
		List<double[]> interectingHyperplanes = new ArrayList<double[]> ();
		
		for(int i=0;i<Configuration.N;i++){
			if (lu_planes[i*2]){//Lower bound
				interectingHyperplanes.add(hyperplaneAtTheBound(i*2));
			}else if(lu_planes[(i*2)+1]){//Upper bound
				interectingHyperplanes.add(hyperplaneAtTheBound((i*2)+1));
			}
		}
		
		//2. Find a list with all the intersecting points for the different hyperplanes
		
			//2.1 The inner point is the best point within the bounds
		double[] inner = simplex.getBest().getCoordinates().getCoordinatesAsDouble();
		
		//System.out.println("----");
		//System.out.println(doubleArrayAsString(vector));
		//System.out.println("----");
			//2.2 we create a list with all the intersecting points between the inner point and all the boundary hyperplanes
		List<double[]> intersectingPoints = new ArrayList<double[]>();
		
		int position = 0;
		int index = 0;
		double smallest_norm = Double.MAX_VALUE;
		for (Iterator<double[]> it= interectingHyperplanes.iterator();it.hasNext();){
			double[] hyperplane = it.next();
			double[] interectingpoint = intersectionOfTwoPointsWithAHyperplane(inner, coordinates, hyperplane);
			intersectingPoints.add(interectingpoint);
			double norm = norm(inner,interectingpoint);
			if(norm < smallest_norm){
				position = index;
				smallest_norm = norm;
			}
			index++;
		}
			//2.3 The intersecting point at the bounds is that one with the smallest norm
		double[] intersectingpoint = intersectingPoints.get(position);
		
		//3. We do linear interpolation with inner,f(inner), intersectingpoint,f(simplex.getArrow.getfitness) in the coordinates
		double fitness=0;
		try{
			
		// Commented: linear interpolation	
		//double slope = (simplex.getArrow().getFitness()-simplex.getBest().getFitness())
		//				/smallest_norm;
		
		//fitness = norm(intersectingpoint,coordinates) * slope + simplex.getArrow().getFitness();
		
		// Exponential interpolation
		double slope = Math.log((simplex.getArrow().getFitness()-simplex.getBest().getFitness()))
				/smallest_norm;

		fitness = Math.pow(Math.pow(Math.E,slope),norm(inner,coordinates));
				

		}catch(Exception e){
			System.err.println("The simplex might not have been initialized with coordinates in the bounds");
		};	
		
		return fitness;
	}
	//return fitness for 
	public static double fitnessWithWallAtTheBounds(double[] coordinates, Simplex simplex){
//		if(true)
//			return simplex.getEvaluator().evaluate(coordinates);
		if(Bounds.isCoordinateOutsideTheBounds(coordinates)){
			if(Configuration.minimization)
				return 10e20;
			else
				return 10e-20;
		}else{
			return simplex.getEvaluator().evaluate(coordinates);
		}
	}
	
	private static double norm(double[] v1,double[] v2){
		double sum = 0;
		for(int i=0;i<v1.length;i++){
			sum += Math.pow(v1[i]-v2[i],2);
		}
		return Math.sqrt(sum);
	}
	
	//position describes the position of the broken bound in the lu_bound matrix
	// It returns the coeficients of the hyperplane
	private static double[] hyperplaneAtTheBound(int position){
		
		double fixedvaluehyperplane = Configuration.bounds[position];
		
		int fixedpositionhyperplane = ((int)(position/2));
		
		// We need the boundary hyperplane defined by n points
		// We first take all the vertices of the hyperplane 
		double[][] newpoints = new double[Configuration.N][Configuration.N];
		int shift = 0;
		for(int i=0;i<Configuration.N;i++){
			for(int j=0;j<Configuration.N;j++){
				if (i == fixedpositionhyperplane){
					newpoints[j][i] = fixedvaluehyperplane;
					shift = 1;
				}else{
					if (i-shift < j){
						newpoints[j][i] = Configuration.bounds[(i*2)+1];	
					}else{
						newpoints[j][i] = Configuration.bounds[(i*2)];
					}
					
				}
			}
		}
				
		//We create a simplex with it
		Simplex boundaryHyperplane = new Simplex();
		//System.out.println("\n-----");
		for(int j=0;j<Configuration.N;j++){
			//System.out.println(doubleArrayAsString(newpoints[j]));
			boundaryHyperplane.addVertexWithoutFitness(newpoints[j]);	
	
		}
		//System.out.println("-------\n");
		
		return Gradients.getBowHyperplane(boundaryHyperplane);
	}
	
	public static String doubleArrayAsString(double[] array){
		String s="";
		for(int i=0;i<array.length;i++){
			s +=array[i]+" ";
		}
		return s;
	}
	
	// We solve a system of ecuations such that:
	// 1/x1-x0 X - 1/y1-y0 Y +       0 Z   =   x0/x1-x0 - y0/y1-y0
	// 1/x1-x0 X +       0 Y - 1/z1-z0 Z   =   x0/x1-x0 - z0/z1-z0
	//       A X +       B Y +       C Z   =   -D
	// where the last equation is the equation of the hyperplane
	private static double[] intersectionOfTwoPointsWithAHyperplane(double[] p0, double[] p1, double[] hyperplane){
		DoubleMatrix A = DoubleMatrix.zeros(Configuration.N, Configuration.N);
		DoubleMatrix B = DoubleMatrix.zeros(Configuration.N, 1);
		
		for(int i=1;i<Configuration.N;i++){
			//A.put(rowIndex, columnIndex, value);
			A.put(i-1, 0, 1.0/(p1[0]-p0[0])); // this is 1/x1-x0
			A.put(i-1, i,-1.0/(p1[i]-p0[i]));
			double value = p0[0]/(p1[0]-p0[0]) - p0[i]/(p1[i]-p0[i]);
			B.put(i-1, 0, value);
		}
		
		//Add the hyperplane in the last row
		
		double[] hyperplane_without_D = new double[Configuration.N];
		System.arraycopy(hyperplane, 0, hyperplane_without_D, 0, Configuration.N);
		A.putRow(Configuration.N-1, new DoubleMatrix(hyperplane_without_D));
		
		B.put(Configuration.N-1, 0, -1.0 * hyperplane[Configuration.N]);
		
		try {
			return Solve.solve(A, B).toArray();	
		} catch (Exception e) { //e.g. if the line is parallel to the hyperplane
			return null;
		}
		
	}
	
	
}
